/*****************************************************************************
*                     Organizacion de Datos - 75.06                          *
*                         Catedra Lic. Servetto                              *
*            Facultad de Ingenieria - Universidad de Buenos Aires            *
******************************************************************************/ 

#include "../Comun/Archivo.h"
#include "ArchivoEnDisco.h"
#include <string>
#include <iostream>

using namespace std;
using namespace Comun;

namespace CapaFisica
{

	/// Representa a un IArchivo que guarda su informacion en archivos en disco.
	/// @param *metadatosArchivos Puntero a los metadatos que definen este archivo.
	/// @param *manejadorEspacioLibre Puntero a una intacia creada del manejador de espacio libre que utilizara este archivo.
	/// @param tamanioBloque Tamanio por defecto de los grupos del archivo.
	ArchivoEnDisco::ArchivoEnDisco(MetadatosArchivo* metadatosArchivo, CapaFisica::ManejadorEspacioLibre* manejadorEspacioLibre, bool corroborarTamanios)
	{
		// Me guardo las propiedades de mi archivo en disco.
		this->Metadatos = metadatosArchivo;
		this->CorroborarTamanios = corroborarTamanios;
		this->ManejadorEspacioLibre = manejadorEspacioLibre;
	};

	/// Destructor por defecto del archivo en disco.
	ArchivoEnDisco::~ArchivoEnDisco()
	{
		// Desctructor vacio.
	};

	/// Devuelve un id de grupo para ser utilizado en un grupo nuevo, ademas setea ese id al grupo pasado por parametro.
	/// @param &grupo El grupo al cual se le creara un id.
	/// @return El id del grupo recien creado.
	int ArchivoEnDisco::CrearGrupo(Comun::Grupo &grupo)
	{
		// Busco un grupoId libre en mi manejador de espacio libre.
		int nuevoGrupoId = this->ManejadorEspacioLibre->ObtenerBloqueLibre(1);
		
		// Actualizo el arhicvo de espacio libre.
		this->ManejadorEspacioLibre->GuardarArchivo();
		
		// Grabo el grupoId en el grupo y lo devuelvo.
		grupo.GrupoId = nuevoGrupoId;
		return nuevoGrupoId;
	};
	
	/// Guarda un grupo en el archivo en disco.
	/// @param &grupo El grupo con los datos que se guardaran en el archivo en disco.
	/// @param omitirSubflujo Desactiva el chequeo de subflujo, siver para poder grabar grupos con menos datos que un subflujo para archivos donde se corrobora el tamanio.
	/// @return Un CodigoResultado indicando el estado de la operacion de guardado.
	CodigoResultado ArchivoEnDisco::GuardarGrupo(Comun::Grupo &grupo, bool omitirSubflujo)
	{ 
		// Mapeo los datos del grupo a un contenedor en disco para poder grabarlo.
		Contenedor& contenedorParaGrupo = *(new Contenedor());

		// Atributos del grupo.
		for(IteradorDeAtributos i = grupo.Atributos.begin(); i != grupo.Atributos.end(); ++i)
		{
			// Copio el atributo de Comun a uno utilizado por CapaFisica
			Comun::Atributo atributo = *i;
			CapaFisica::Atributo& atributoIntermedio = *(new CapaFisica::Atributo());
			atributoIntermedio.Nombre = &atributo.Nombre;
			atributoIntermedio.Tipo = ParserDeTipoRegistro::ConvertirACaracter(atributo.Tipo);
			atributoIntermedio.Valor = new string(atributo.Valor.c_str());
			contenedorParaGrupo.Atributos->push_back(atributoIntermedio);
		}
		
		// Registros del grupo.
		for(IteradorDeRegistros i = grupo.Registros.begin(); i != grupo.Registros.end(); ++i)
		{
			Contenedor& contenedorParaRegistro = *(new Contenedor());
			Comun::Registro registro = *i;
			
			// Atributos del grupo.
			for(IteradorDeAtributos j = registro.Atributos.begin(); j != registro.Atributos.end(); ++j)
			{
				// Copio el atributo de Comun a uno utilizado por CapaFisica
				Comun::Atributo atributo = *j;
				CapaFisica::Atributo& atributoIntermedio = *(new CapaFisica::Atributo());
				atributoIntermedio.Nombre = &atributo.Nombre;
				atributoIntermedio.Tipo = ParserDeTipoRegistro::ConvertirACaracter(atributo.Tipo);
				atributoIntermedio.Valor = new string(atributo.Valor.c_str());
				contenedorParaRegistro.Atributos->push_back(atributoIntermedio);
			}
			contenedorParaGrupo.Hijos->push_back(contenedorParaRegistro);
		}

		// Serializo el contenedor en el principio del buffer.
		SerializadorContenedores* ser = new SerializadorContenedores(&contenedorParaGrupo);
		char* buffer = new char[this->Metadatos->TamanioBloque * 2]; // Factor se seguridad del doble de tamanio por si el grupo es muy grande.
		int resultadoSerializar = ser->Serializar(buffer, 0);
		int tamanioContenedor = contenedorParaGrupo.Tamanio;

		// Actualizo el tamanio de los registros
		list<Contenedor>::const_iterator c = contenedorParaGrupo.Hijos->begin();
		IteradorDeRegistros i = grupo.Registros.begin();
		for(IteradorDeRegistros i = grupo.Registros.begin(); i != grupo.Registros.end(); ++i)
		{
			// Actualizo el campo de tamanio con el actual
			(*i).Tamanio = (*c).Tamanio;
			
			// Paso al proximo contenedor
			++c;
		}

		// Libero los recursos del contenedor y serializador que ya no son mas necesarios.
		delete( ser );
		delete( &contenedorParaGrupo );

		// Si estoy trabajando con un archivo por ejemplo de longitudes variables de registros, donde tengo que
		// corroborar el tamanio antes de guardar en disco, lo hago, si no, simplemente paso a el grabado de los datos.
		if (this->CorroborarTamanios)
		{
			double porcentajeCompleto = ((double)ArchivoEnDisco::PorcentajeDeCompleto) / ((double)100);
			double porcentajeVacio = ((double)ArchivoEnDisco::PorcentajeDeVacio) / ((double)100);

			// Reviso que el tamanio real del contenedor no supere el porcetaje maximo del bloque para que tenga un sobreflujo.
			if (tamanioContenedor > this->Metadatos->TamanioBloque * porcentajeCompleto)
				return Sobreflujo;

			// Reviso que el tamanio real del contenedor no sea menor que el porcetaje minimo para que tenga un subflujo,
			// Igualmente si llame al metodo pasandole un omitirSubflujo = true, no reviso la condicion de Subflujo.
			if ((!omitirSubflujo) && (tamanioContenedor < this->Metadatos->TamanioBloque * porcentajeVacio))
				return Subflujo;
		}

		// Abro el archivo
		ofstream file;
		string nombre = "Datos/" + this->Metadatos->Nombre + ".datos.txt";
		file.open(nombre.c_str(), ios::app | ios::binary);
		file.close();
		// Importante: Incluir ios::in, ya que ambos son necesarios para poder sobreescribir.
		// sin el ios::in, no lee el archivo en el buffer y cuando graba graba solo lo que se puso en el buffer.
		// ios:ate manda al fin de archivo. 
		file.open(nombre.c_str(), ios::binary | ios::out | ios::in );
		
		// Calculo la posiscion donde voy a meter el contenedor serializado y voy hasta ella.
		int offset = grupo.GrupoId * this->Metadatos->TamanioBloque;
		// Cuando se usa ios_base:beg, toma el offset a partir de la variable begin, que en modo append es el fin de archivo	
		file.seekp(offset);

		// Grabo el buffer de datos con el contenedor serializado y cierro el archivo.
		file.write(buffer, this->Metadatos->TamanioBloque);
		file.close();

		this->ManejadorEspacioLibre->AsignarTamanio(grupo.GrupoId,tamanioContenedor);
		this->ManejadorEspacioLibre->GuardarArchivo();

		// Como todo salio bien, devolvulevo que la operacion resulto con exito.
		return Exito;
	};
	
	/// Leo un grupo almacenado en el archivo en disco.
	/// @param grupoId El identificador del grupo que quiero leer.
	/// @return Devuelve un grupo con todos los datos que leyo de el.
	Comun::Grupo& ArchivoEnDisco::LeerGrupo(int grupoId)
	{
		// Reviso que el grupo existe y no este vacio, en caso controrio, informo del error.
		if (this->ManejadorEspacioLibre->EstaLibre(grupoId) || !this->ManejadorEspacioLibre->EstaInicializado(grupoId))
		{
			cerr << "CapaFisica->ArchivoEnDisco.cpp->ArchivoEnDisco::LeerGrupo(int grupoId)" << endl << "El grupo que se quiere leer esta libre o no esta inicializado." << endl;
			throw new exception();
		}
		
		// Creo una estructura de contenedores con la metadata del archivo.
		Contenedor& contenedorParaGrupo = *(new Contenedor());
		for(IteradorDeMetadatosDeAtributos i = this->Metadatos->MetadatosGrupo->MetadatosAtributos.begin(); i != this->Metadatos->MetadatosGrupo->MetadatosAtributos.end(); ++i)
		{
			Comun::MetadatosAtributo atributo = *i;
			CapaFisica::Atributo& atributoIntermedio = *(new CapaFisica::Atributo());
			atributoIntermedio.Nombre = new string(atributo.Nombre);
			atributoIntermedio.Tipo = ParserDeTipoRegistro::ConvertirACaracter(atributo.Tipo);
			contenedorParaGrupo.Atributos->push_back(atributoIntermedio);
		}
		
		contenedorParaGrupo.Hijos = new list<Contenedor>();
		Contenedor& contenedorParaRegistro = *(new Contenedor());
		for(IteradorDeMetadatosDeAtributos j = this->Metadatos->MetadatosGrupo->MetadatosRegistro->MetadatosAtributos.begin(); j != this->Metadatos->MetadatosGrupo->MetadatosRegistro->MetadatosAtributos.end(); ++j)
		{
			Comun::MetadatosAtributo atributo = *j;
			CapaFisica::Atributo& atributoIntermedio = *(new CapaFisica::Atributo());
			atributoIntermedio.Nombre = new string(atributo.Nombre);
			atributoIntermedio.Tipo = ParserDeTipoRegistro::ConvertirACaracter(atributo.Tipo);
			contenedorParaRegistro.Atributos->push_back(atributoIntermedio);
		}
		contenedorParaGrupo.Hijos->push_back(contenedorParaRegistro);
		
		// Creo un SerializadorContendores con la metadata recien creada para deserizalizar el grupo.
		SerializadorContenedores* ser = new SerializadorContenedores(&contenedorParaGrupo);
		
		// Calculo el offset del grupo a leer y me creo un buffer para volcar sus datos.
		int offset = grupoId * this->Metadatos->TamanioBloque;
		char* buffer = new char[this->Metadatos->TamanioBloque];
		
		// Abro el archivo en modo lectura, voy hasta la pocision se encuentra el grupo y lo vuelco en el buffer de datos.
		ifstream file;
		string nombre = "Datos/" + this->Metadatos->Nombre + ".datos.txt";
		file.open(nombre.c_str(), ios::in | ios::binary);
		file.seekg(offset, ios::beg);
		file.read(buffer, this->Metadatos->TamanioBloque);
		file.close();
		
		// Deserializo el buffer en el contenedor.
		ser->Deserializar(buffer, 0);
		
		// Transformo los contenedores nuevamente en un Grupo con Registros.
		typedef list<Atributo>::const_iterator LIA;
		typedef list<Contenedor>::const_iterator LIC;
		
		Comun::Grupo* grupoDevuelto = new Comun::Grupo();
		grupoDevuelto->GrupoId = grupoId;
		
		// Transformo los atributos del contenedor principal en atributos del grupo.
		for (LIA i = contenedorParaGrupo.Atributos->begin(); i != contenedorParaGrupo.Atributos->end(); ++i)
		{
			CapaFisica::Atributo atributo = *i;
			Comun::Atributo* atributoDeGrupo = new Comun::Atributo();
			atributoDeGrupo->Nombre = (*new string(*atributo.Nombre));
			atributoDeGrupo->Tipo = ParserDeTipoRegistro::Parsear(atributo.Tipo);
			atributoDeGrupo->Valor = (*new string(*atributo.Valor));
			grupoDevuelto->Atributos.push_back(*atributoDeGrupo);
		}
		
		// Transformo los contenedores hijos en registros.
		for (LIC j = contenedorParaGrupo.Hijos->begin(); j != contenedorParaGrupo.Hijos->end(); ++j)
		{
			// Transformo los atributos del contenedor en atributos del registro.
			Contenedor contendorHijo = *j;
			Comun::Registro* registroDelGrupoDevuelto = new Comun::Registro();
			registroDelGrupoDevuelto->Tamanio = contendorHijo.Tamanio;
			for (LIA i = contendorHijo.Atributos->begin(); i != contendorHijo.Atributos->end(); ++i)
			{
				CapaFisica::Atributo atributo = *i;
				Comun::Atributo* atributoDeGrupo = new Comun::Atributo();
				atributoDeGrupo->Nombre = (*new string(*atributo.Nombre));
				atributoDeGrupo->Tipo = ParserDeTipoRegistro::Parsear(atributo.Tipo);
				atributoDeGrupo->Valor = (*new string(*atributo.Valor));
				registroDelGrupoDevuelto->Atributos.push_back(*atributoDeGrupo);
			}
			grupoDevuelto->Registros.push_back(*registroDelGrupoDevuelto);
		}
		
		delete( ser );
		
		// Devuelvo el grupo con los datos.
		return *grupoDevuelto;
	};
	
	/// Borro un grupo del arhcivo en disco. Esto lo marca la lista de grupos libres para poder usar nuevamente ese grupoId en otro grupo.
	/// @param grupoId El identificador del grupo a liberar.
	void ArchivoEnDisco::BorrarGrupo(int grupoId)
	{
		// Libero el grupo.
		this->ManejadorEspacioLibre->Liberar(grupoId);
		
		// Actualizo el archivo.
		this->ManejadorEspacioLibre->GuardarArchivo();
	};
	
	/// Obtiene el primer grupoId del archivo. Util para recorridos secuenciales.
	/// @return El grupoId del primer grupo del archivo en disco.
	int ArchivoEnDisco::PrimerGrupo()
	{ 
		// Busco el siguiente grupo al grupo -1 que simbolizaria un grupo anterior al grupo 0, que es el primero disponible al usuario.
		return this->SiguienteGrupo(-1);
	};
	
	/// Obtiene el primer grupoId del archivo. Util para recorridos secuenciales.
	/// @param grupoIdActual El grupoId desde donde devolver el siguente grupoId.
	/// @return El grupoId del siguiente grupo al que posee un grupoId que se le paso por parametro.
	int ArchivoEnDisco::SiguienteGrupo(int grupoIdActual)
	{
		// Me posiciono en el siguiente grupo al que me pasaron
		int i = grupoIdActual + 1;
		bool seguir = true;
		while(seguir)
		{
			if (!this->ManejadorEspacioLibre->EstaInicializado(i))
			{
				// Si el grupo no esta inicializado ya no hay mas grupos, ya que de ahi en mas todos los demas grupos tampoco estaran inicializados.
				i = NoHayMasGrupos;
				break;
			}
			else if (this->ManejadorEspacioLibre->EstaLibre(i))
				// Si el grupo esta libre lo salteo
				++i;
			else
				// Si es un grupo que no esta inicializado y no es uno libre, encontre el siguiente.
				seguir = false;
		}
		
		// Devuelvo el grupo encontrado.
		return i;
	};
	
}
